
/*
 * Copyright (c) 2010 Sergey N Frolov
 * 
 * See LICENSE file
 */

# ifdef NGX_JAVASCRIPT_HAVE_SQLITE

#include "ngx_javascript_module_sqlite.h"

static JSFunctionSpec ngx_javascript_sqlite_methods[] = {
	{ "prepare", ngx_javascript_sqlite_prepare, 0, 0, 0 },
	{ "execute", ngx_javascript_sqlite_execute, 0, 0, 0 },
	{ "query", ngx_javascript_sqlite_execute, 0, 0, 0 },
	{ "escape", ngx_javascript_sqlite_escape, 0, 0, 0 },
	{ "quote", ngx_javascript_sqlite_quote, 0, 0, 0 },
	{ "close", ngx_javascript_sqlite_close, 0, 0, 0 },
	{ 0, 0, 0, 0, 0 }
};

static JSFunctionSpec ngx_javascript_sqlite_static_methods[] = {
	{ "escape", ngx_javascript_sqlite_escape, 0, 0, 0 },
	{ "quote", ngx_javascript_sqlite_quote, 0, 0, 0 },
	{ 0, 0, 0, 0, 0 }
};

static JSFunctionSpec ngx_javascript_sqlite_result_methods[] = {
	{ "bind", ngx_javascript_sqlite_result_bind, 0, 0, 0 },
	{ "execute", ngx_javascript_sqlite_result_execute, 0, 0, 0 },
	{ "length", ngx_javascript_sqlite_result_length, 0, 0, 0 },
	{ "fetchObject", ngx_javascript_sqlite_result_fetchObject, 0, 0, 0 },
	{ "fetchArray", ngx_javascript_sqlite_result_fetchArray, 0, 0, 0 },
	{ "fetch", ngx_javascript_sqlite_result_fetchObject, 0, 0, 0 },
	{ "finish", ngx_javascript_sqlite_result_finish, 0, 0, 0 },
	{ 0, 0, 0, 0, 0 }
};

static JSClass ngx_javascript_sqlite_class = {
	"SQLite",
	JSCLASS_HAS_PRIVATE,
	JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
	JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ngx_javascript_sqlite_finalize,
	JSCLASS_NO_OPTIONAL_MEMBERS
};

static JSClass ngx_javascript_sqlite_result_class = {
	"SQLiteResult",
	JSCLASS_HAS_PRIVATE,
	JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
	JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ngx_javascript_sqlite_result_finalize,
	JSCLASS_NO_OPTIONAL_MEMBERS
};

JSBool ngx_javascript_init_sqlite(JSContext *jcx, JSObject *global) {
	jsval result;
	if (JS_InitClass(jcx, global, NULL, &ngx_javascript_sqlite_class, ngx_javascript_sqlite, 0, NULL, ngx_javascript_sqlite_methods, NULL, ngx_javascript_sqlite_static_methods) == NULL)
		return ngx_javascript_outofmemory(jcx);
	if (JS_InitClass(jcx, global, NULL, &ngx_javascript_sqlite_result_class, NULL, 0, NULL, ngx_javascript_sqlite_result_methods, NULL, NULL) == NULL)
		return ngx_javascript_outofmemory(jcx);
	if (JS_LookupProperty(jcx, global, "SQLite", &result) == JS_TRUE && JSVAL_IS_OBJECT(result) && !JSVAL_IS_NULL(result)) {
		JSObject *sqlite_class = JSVAL_TO_OBJECT(result);
		JS_DefineProperty(jcx, sqlite_class, "version", STRING_TO_JSVAL(JS_NewStringCopyN(jcx, SQLITE_VERSION, sizeof(SQLITE_VERSION) - 1)), NULL, NULL, 0);
	}
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	char *filename;
	sqlite3 *db;
	int flags;
	int ok;
	JSString *str;
	if (argc < 1) return ngx_javascript_sqlite_error(jcx, 0, "File is not String");
	if (argc == 1) {
		flags = SQLITE_OPEN_READWRITE;
	} else {
		if (JS_ValueToInt32(jcx, argv[1], &flags) == JS_FALSE) {
			flags = 0;
		}
	}
	str = JS_ValueToString(jcx, argv[0]);
	if (str == NULL) return ngx_javascript_sqlite_error(jcx, 0, "File is not String");
	filename = JS_GetStringBytes(str);
	if (filename[0] == '/') {
		if (ngx_javascript_check_basedir(jcx, filename, flags & SQLITE_OPEN_CREATE ? O_CREAT : 0) == JS_FALSE) return ngx_javascript_sqlite_error(jcx, ENOENT, "No such file or directory");
		ok = sqlite3_open_v2(filename, &db, flags, NULL);
	} else {
		char fullname[PATH_MAX];
		if (ngx_javascript_fullpath(jcx, filename, fullname) == JS_FALSE) return JS_FALSE;
		if (ngx_javascript_check_basedir(jcx, fullname, flags & SQLITE_OPEN_CREATE ? O_CREAT : 0) == JS_FALSE) return ngx_javascript_sqlite_error(jcx, ENOENT, "No such file or directory");
		ok = sqlite3_open_v2(fullname, &db, flags, NULL);
	}
	if (ok == SQLITE_OK) {
		JS_SetPrivate(jcx, obj, (void *) db);
		JS_DefineProperty(jcx, obj, "database", STRING_TO_JSVAL(JS_NewStringCopyZ(jcx, filename)), NULL, NULL, JSPROP_ENUMERATE);
	} else if (db != NULL) {
		return ngx_javascript_sqlite_error(jcx, ok, sqlite3_errmsg(db));
	} else {
		return ngx_javascript_sqlite_error(jcx, 0, "Open failed");
	}
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite_prepare(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	return ngx_javascript_sqlite_query(jcx, obj, argc, argv, rval, NGX_JAVASCRIPT_SQLITE_PREPARE);
}

JSBool ngx_javascript_sqlite_execute(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	return ngx_javascript_sqlite_query(jcx, obj, argc, argv, rval, NGX_JAVASCRIPT_SQLITE_EXECUTE);
}

JSBool ngx_javascript_sqlite_query(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval, int todo) {
	JSObject *r;
	JSString *str;
	sqlite3 *db;
	sqlite3_stmt *st;
	const void *sql, *tail;
	int ok;
	if (argc < 1) return ngx_javascript_sqlite_error(jcx, 0, "Query is empty");
	db = JS_GetPrivate(jcx, obj);
	if (db == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Database is closed");
	str = JS_ValueToString(jcx, argv[0]);
	if (str == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Query is empty");
	sql = (const void *) JS_GetStringChars(JS_ValueToString(jcx, argv[0]));
	if (sql == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Query is empty");
	ok = sqlite3_prepare16_v2(db, sql, JS_GetStringLength(str) * sizeof(jschar), &st, &tail);
	if (ok == SQLITE_OK) {
		r = JS_NewObject(jcx, &ngx_javascript_sqlite_result_class, NULL, NULL);
		if (r == NULL) return ngx_javascript_outofmemory(jcx);
		JS_SetPrivate(jcx, r, (void *) st);
		JS_DefineProperty(jcx, r, "db", argv[-1], NULL, NULL, JSPROP_ENUMERATE);
		JS_DefineProperty(jcx, r, "sql", argv[0], NULL, NULL, JSPROP_ENUMERATE);
		JS_DefineProperty(jcx, r, "step", JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
		JS_DefineProperty(jcx, r, "count", JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
		JS_DefineProperty(jcx, r, "position", JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
		*rval = OBJECT_TO_JSVAL(r);
		if (todo == NGX_JAVASCRIPT_SQLITE_EXECUTE) {
			if (argc > 1 && ngx_javascript_sqlite_result_bind(jcx, r, argc - 1, argv + 1, rval) == JS_FALSE) return JS_FALSE;
			ok = sqlite3_step(st);
			JS_DefineProperty(jcx, r, "step", INT_TO_JSVAL(ok), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
			switch (ok) {
				case SQLITE_ROW:
					JS_DefineProperty(jcx, r, "position", INT_TO_JSVAL(0), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
				case SQLITE_DONE:
					break;
				default:
					return ngx_javascript_sqlite_error(jcx, ok, sqlite3_errmsg(db));
			}
		}
	} else {
		return ngx_javascript_sqlite_error(jcx, ok, sqlite3_errmsg(db));
	}
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite_result_bind(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	jsval result;
	sqlite3_stmt *st;
	jsuint i;
	st = JS_GetPrivate(jcx, obj);
	if (st == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Query is not prepared");
	sqlite3_clear_bindings(st);
	if (argc < 1) {
		return JS_TRUE;
	} else if (argc == 1 && JSVAL_IS_OBJECT(argv[0]) && !JSVAL_IS_NULL(argv[0])) {
		JSObject *o = JSVAL_TO_OBJECT(argv[0]);
		if (JS_IsArrayObject(jcx, o) == JS_TRUE) {
			jsuint length;
			if (JS_GetArrayLength(jcx, o, &length) == JS_TRUE) {
				// Bind params from Array
				for (i = 0; i < length; i ++) {
					if (JS_LookupElement(jcx, o, i, &result) == JS_TRUE) {
						if (ngx_javascript_sqlite_result_bind_value(jcx, st, i + 1, result) == JS_FALSE) return JS_FALSE;
					} else {
						if (ngx_javascript_sqlite_result_bind_value(jcx, st, i + 1, JSVAL_NULL) == JS_FALSE) return JS_FALSE;
					}
				}
			}
		} else {
			return ngx_javascript_sqlite_error(jcx, 0, "Param is not Array");
		}
	} else {
		// Bind params from arguments
		for (i = 0; i < argc; i ++) {
			if (ngx_javascript_sqlite_result_bind_value(jcx, st, i + 1, argv[i]) == JS_FALSE) return JS_FALSE;
		}
	}
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite_result_bind_value(JSContext *jcx, sqlite3_stmt *st, jsuint i, jsval result) {
	int ok;
	if (JSVAL_IS_NULL(result) || JSVAL_IS_VOID(result)) {
		ok = sqlite3_bind_null(st, i);
		if (ok != SQLITE_OK) goto javascript_bind_value_error;
	} else if (JSVAL_IS_BOOLEAN(result)) {
		ok = sqlite3_bind_int(st, i, JSVAL_TO_BOOLEAN(result));
		if (ok != SQLITE_OK) goto javascript_bind_value_error;
	} else if (JSVAL_IS_INT(result)) {
		ok = sqlite3_bind_int(st, i, JSVAL_TO_INT(result));
		if (ok != SQLITE_OK) goto javascript_bind_value_error;
	} else if (JSVAL_IS_STRING(result)) {
		JSString *str = JSVAL_TO_STRING(result);
		if (str == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Param is not String");
		ok = sqlite3_bind_text16(st, i, (void *) JS_GetStringChars(str), JS_GetStringLength(str) * sizeof(jschar), SQLITE_TRANSIENT);
		if (ok != SQLITE_OK) goto javascript_bind_value_error;
	} else {
		JSString *str = JS_ValueToString(jcx, result);
		if (str == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Param is not String");
		ok = sqlite3_bind_text16(st, i, (void *) JS_GetStringChars(str), JS_GetStringLength(str) * sizeof(jschar), SQLITE_TRANSIENT);
		if (ok != SQLITE_OK) goto javascript_bind_value_error;
	}
	return JS_TRUE;
	javascript_bind_value_error:
	return ngx_javascript_sqlite_error(jcx, ok, sqlite3_errmsg(sqlite3_db_handle(st)));
}

JSBool ngx_javascript_sqlite_result_fetchArray(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	return ngx_javascript_sqlite_result_fetch_value(jcx, obj, argc, argv, rval, NGX_JAVASCRIPT_SQLITE_FETCH_ARRAY);
}

JSBool ngx_javascript_sqlite_result_fetchObject(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	return ngx_javascript_sqlite_result_fetch_value(jcx, obj, argc, argv, rval, NGX_JAVASCRIPT_SQLITE_FETCH_OBJECT);
}

JSBool ngx_javascript_sqlite_result_fetch_value(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval, int todo) {
	jsval result;
	sqlite3_stmt *st;
	jsuint i, count;
	int ok;
	st = JS_GetPrivate(jcx, obj);
	if (st == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Query is not prepared");
	if (JS_LookupProperty(jcx, obj, "step", &result) == JS_TRUE && JSVAL_IS_INT(result)) {
		if (JSVAL_TO_INT(result) != SQLITE_ROW) {
			*rval = JSVAL_NULL;
			return JS_TRUE;
		}
	} else {
		return ngx_javascript_sqlite_error(jcx, 0, "Query is not executed");
	}
	if (todo == NGX_JAVASCRIPT_SQLITE_FETCH_ARRAY) {
		JSObject *r = JS_NewArrayObject(jcx, 0, NULL);
		if (r == NULL) return ngx_javascript_outofmemory(jcx);
		count = sqlite3_column_count(st);
		JS_SetArrayLength(jcx, r, count);
		for (i = 0; i < count; i ++) {
			JS_DefineElement(jcx, r, i, ngx_javascript_sqlite_get_value(jcx, st, i), NULL, NULL, JSPROP_ENUMERATE);
		}
		*rval = OBJECT_TO_JSVAL(r);
	} else {
		JSObject *r = JS_NewObject(jcx, NULL, NULL, NULL);
		if (r == NULL) return ngx_javascript_outofmemory(jcx);
		count = sqlite3_column_count(st);
		JS_SetArrayLength(jcx, r, count);
		for (i = 0; i < count; i ++) {
			JS_DefineProperty(jcx, r, sqlite3_column_name(st, i), ngx_javascript_sqlite_get_value(jcx, st, i), NULL, NULL, JSPROP_ENUMERATE);
		}
		*rval = OBJECT_TO_JSVAL(r);
	}
	ok = sqlite3_step(st);
	JS_DefineProperty(jcx, obj, "step", INT_TO_JSVAL(ok), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
	switch (ok) {
		case SQLITE_ROW:
			if (JS_LookupProperty(jcx, obj, "position", &result) == JS_TRUE && JSVAL_IS_INT(result)) {
				result = INT_TO_JSVAL(JSVAL_TO_INT(result) + 1);
				JS_DefineProperty(jcx, obj, "position", result, NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
			}
		case SQLITE_DONE:
			break;
		default:
			return ngx_javascript_sqlite_error(jcx, ok, sqlite3_errmsg(sqlite3_db_handle(st)));
	}
	return JS_TRUE;
}

jsval ngx_javascript_sqlite_get_value(JSContext *jcx, sqlite3_stmt *st, int i) {
	jsval result;
	switch (sqlite3_column_type(st, i)) {
		case SQLITE_NULL:
			result = JSVAL_NULL;
			break;
		case SQLITE_INTEGER:
			result = INT_TO_JSVAL(sqlite3_column_int(st, i));
			break;
		case SQLITE_FLOAT:
		{
			double d = sqlite3_column_double(st, i);
			result = DOUBLE_TO_JSVAL(&d);
			break;
		}
		case SQLITE_TEXT:
			result = STRING_TO_JSVAL(JS_NewUCStringCopyN(jcx, (const void *) sqlite3_column_text16(st, i), sqlite3_column_bytes16(st, i) / sizeof(jschar)));
			break;
		case SQLITE_BLOB:
			result = STRING_TO_JSVAL(JS_NewStringCopyN(jcx, (const char *) sqlite3_column_blob(st, i), sqlite3_column_bytes(st, i)));
			break;
		default:
			result = JSVAL_VOID;
	}
	return result;
}

JSBool ngx_javascript_sqlite_result_execute(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	sqlite3_stmt *st;
	int ok;
	st = JS_GetPrivate(jcx, obj);
	if (st == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Query is not prepared");
	sqlite3_reset(st);
	if (argc > 0 && ngx_javascript_sqlite_result_bind(jcx, obj, argc, argv, rval) == JS_FALSE) return JS_FALSE;
	ok = sqlite3_step(st);
	JS_DefineProperty(jcx, obj, "step", INT_TO_JSVAL(ok), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
	JS_DefineProperty(jcx, obj, "count", JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
	JS_DefineProperty(jcx, obj, "position", JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
	switch (ok) {
		case SQLITE_ROW:
			JS_DefineProperty(jcx, obj, "position", INT_TO_JSVAL(0), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
		case SQLITE_DONE:
			break;
		default:
			return ngx_javascript_sqlite_error(jcx, ok, sqlite3_errmsg(sqlite3_db_handle(st)));
	}
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite_result_length(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	sqlite3_stmt *st;
	jsval result;
	int ok;
	st = JS_GetPrivate(jcx, obj);
	if (st == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Query is not prepared");
	if (JS_LookupProperty(jcx, obj, "count", &result) == JS_TRUE && JSVAL_IS_INT(result)) {
		*rval = result;
		return JS_TRUE;
	}
	if (JS_LookupProperty(jcx, obj, "step", &result) == JS_TRUE && JSVAL_IS_INT(result)) {
		ok = JSVAL_TO_INT(result);
		if (ok == SQLITE_DONE) {
			if (JS_LookupProperty(jcx, obj, "position", &result) == JS_TRUE && JSVAL_IS_INT(result)) {
				*rval = result;
				return JS_TRUE;
			} else {
				*rval = JSVAL_ZERO;
				return JS_TRUE;
			}
		}
		if (ok == SQLITE_ROW) {
			int i;
			int fetched = 1;
			int remains = 0;
			if (JS_LookupProperty(jcx, obj, "position", &result) == JS_TRUE && JSVAL_IS_INT(result)) {
				fetched += JSVAL_TO_INT(result);
			}
			while (sqlite3_step(st) == SQLITE_ROW) {
				remains ++;
			}
			sqlite3_reset(st);
			for (i = 0; i < fetched; i ++) sqlite3_step(st);
			*rval = INT_TO_JSVAL(fetched + remains);
			return JS_TRUE;
		}
	}
	*rval = JSVAL_ZERO;
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite_close(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	ngx_javascript_sqlite_finalize(jcx, obj);
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite_result_finish(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	ngx_javascript_sqlite_result_finalize(jcx, obj);
	return JS_TRUE;
}

void ngx_javascript_sqlite_finalize(JSContext *jcx, JSObject *obj) {
	sqlite3 *db = JS_GetPrivate(jcx, obj);
	if (db != NULL) {
		sqlite3_close(db);
		JS_SetPrivate(jcx, obj, NULL);
	}
}

void ngx_javascript_sqlite_result_finalize(JSContext *jcx, JSObject *obj) {
	sqlite3_stmt *st = JS_GetPrivate(jcx, obj);
	if (st != NULL) {
		sqlite3 *db = sqlite3_db_handle(st);
		sqlite3_finalize(st);
		sqlite3_close(db);
		JS_SetPrivate(jcx, obj, NULL);
	}
}

JSBool ngx_javascript_sqlite_error(JSContext *jcx, int code, const char *message) {
	if (code != 0) {
		if (message != NULL) {
			JS_ReportError(jcx, "%s (%d)", message, code);
		} else {
			JS_ReportError(jcx, "(%d)", code);
		}
	} else {
		if (message != NULL) {
			JS_ReportError(jcx, "%s", message);
		} else {
			JS_ReportError(jcx, "...");
		}
	}
	return JS_FALSE;
}

JSBool ngx_javascript_sqlite_quote(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	JSString *input, *output;
	jschar *old, *str;
	jsuint old_len, str_len, i, j;
	if (argc < 1) return JS_TRUE;
	input = JS_ValueToString(jcx, argv[0]);
	if (input == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Param is not String");
	old = JS_GetStringChars(input);
	old_len = str_len = JS_GetStringLength(input);
	for (i = 0; i < old_len; i ++) if (old[i] == 39) str_len ++;
	str_len += 2;
	str = JS_malloc(jcx, str_len * sizeof(jschar));
	if (str == NULL) return ngx_javascript_outofmemory(jcx);
	j = 0;
	str[j] = 39; j ++;
	for (i = 0; i < old_len; i ++) {
		if (old[i] == 39) {
			str[j] = 39; j ++;
			str[j] = 39; j ++;
		} else {
			str[j] = old[i]; j ++;
		}
	}
	str[j] = 39; j ++;
	output = JS_NewUCString(jcx, str, str_len);
	if (output == NULL) return ngx_javascript_outofmemory(jcx);
	*rval = STRING_TO_JSVAL(output);
	return JS_TRUE;
}

JSBool ngx_javascript_sqlite_escape(JSContext *jcx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
	JSString *input, *output;
	jschar *old, *str;
	jsuint old_len, str_len, i, j;
	if (argc < 1) return JS_TRUE;
	input = JS_ValueToString(jcx, argv[0]);
	if (input == NULL) return ngx_javascript_sqlite_error(jcx, 0, "Param is not String");
	old = JS_GetStringChars(input);
	old_len = str_len = JS_GetStringLength(input);
	for (i = 0; i < old_len; i ++) if (old[i] == '\'') str_len ++;
	str = JS_malloc(jcx, str_len * sizeof(jschar));
	if (str == NULL) return ngx_javascript_outofmemory(jcx);
	j = 0;
	for (i = 0; i < old_len; i ++) {
		if (old[i] == '\'') {
			str[j] = '\''; j ++;
			str[j] = '\''; j ++;
		} else {
			str[j] = old[i]; j ++;
		}
	}
	output = JS_NewUCString(jcx, str, str_len);
	if (output == NULL) return ngx_javascript_outofmemory(jcx);
	*rval = STRING_TO_JSVAL(output);
	return JS_TRUE;
}

# endif
